package wallet

import (
	"crypto/ecdsa"
	"crypto/elliptic"
	"crypto/rand"
	"log"
	"crypto/sha256"
	"golang.org/x/crypto/ripemd160"
	"github.com/blockchain_go/part_5/block"
	"bytes"
	"blockchain.com/part_5/utils"
)

const (
 	version = byte(0x00)
 	walletFile = "wallet.dat"
 	addressChecksumLen = 4
)

/*
	一、（1）定义钱包
一个钱包只有一个密钥对。
*/
type Wallet struct {
/*
	（1）ecdsa包实现了椭圆曲线数字签名算法。
	（2）type PrivateKey struct {
    		PublicKey
    		D   *big.Int
		}
		PrivateKey代表一个ECDSA私钥。
*/
	PrivateKey 	ecdsa.PrivateKey
	PublicKey 	[]byte
}



/*
	二、(1)构造新的钱包密钥对
*/
func NewWallet()*Wallet{
	private,public := newKeyPair()
	wallet := Wallet{private,public}

	return &wallet
}

/*
	二、(2)实现新的钱包密钥对
*/
func newKeyPair()(ecdsa.PrivateKey,[]byte)  {
/*
	（1）crypto包搜集了常用的密码（算法）常量。
	（2）elliptic包实现了几条覆盖素数有限域的标准椭圆曲线。
	（3）func P256() Curve
		返回一个实现了P-256的曲线。
*/
	curve := elliptic.P256()
/*
	（1）GenerateKey函数生成一对
	func GenerateKey(c elliptic.Curve, rand io.Reader) (priv *PrivateKey, err error)
	公钥/私钥。

	（2）rand包实现了用于加解密的更安全的随机数生成器。
	Reader是一个全局、共享的密码用强随机数生成器。在Unix类型系统中，会从/dev/urandom读取；
而Windows中会调用CryptGenRandom API。
*/
	private,err := ecdsa.GenerateKey(curve,rand.Reader)// 使用椭圆生成一个私钥
	if err != nil {
		 log.Panic(err)
	}

	// 通过私钥生成一个公钥：公钥是椭圆曲线上面的点，即是X,Y坐标的组合，比特币中，这些坐标会连接起来，形成一个公钥
	pubKey := append(private.PublicKey.X.Bytes(),private.PublicKey.Y.Bytes()...)

	return *private,pubKey
}

/* 三、（1）生成地址
一个公钥转换成一个 Base58 地址需要以下步骤：
A:使用 RIPEMD160(SHA256(PubKey)) 哈希算法，取公钥并对其哈希两次
B:给哈希加上地址生成算法版本的前缀
C:对于第二步生成的结果，使用 SHA256(SHA256(payload)) 再哈希，计算校验和。
校验和是结果哈希的前四个字节。
D:将校验和附加到 version+PubKeyHash 的组合中。
E:使用 Base58 对 version+PubKeyHash+checksum 组合进行编码。
*/
func (w Wallet)GetAddress()[]byte  {
	pubKeyHash := HashPubKey(w.PublicKey)

	versionedPayload := append([]byte{version},pubKeyHash...)
	checksum := checksum(versionedPayload)

	fullPayload := append(versionedPayload,checksum...)
	address := block.Base58Encode(fullPayload)

	return address
}

// 三、（2）实现公钥哈希
func HashPubKey(pubKey []byte)[]byte  {
	publicSHA256 := sha256.Sum256(pubKey)
	// 返回一个新的哈希计算校验和
	RIPEMD160Hasher := ripemd160.New()
/*
	type Writer interface {
    	Write(p []byte) (n int, err error)
	}
	Writer接口用于包装基本的写入方法。

Write方法len(p) 字节数据从p写入底层的数据流。它会返回写入的字节数(0 <= n <= len(p))和遇到的任何导致写入提取结束的错误。
Write必须返回非nil的错误，如果它返回的 n < len(p)。Write不能修改切片p中的数据，即使临时修改也不行。
*/
	_,err := RIPEMD160Hasher.Write(publicSHA256[:])
	if err != nil {
		log.Panic(err)
	}

/*
	type Hash interface {
		// 通过嵌入的匿名io.Writer接口的Write方法向hash中添加更多数据，永远不返回错误
		io.Writer
		// 返回添加b到当前的hash值后的新切片，不会改变底层的hash状态
		Sum(b []byte) []byte
		// 重设hash为无数据输入的状态
		Reset()
		// 返回Sum会返回的切片的长度
		Size() int
		// 返回hash底层的块大小；Write方法可以接受任何大小的数据，
		// 但提供的数据是块大小的倍数时效率更高
		BlockSize() int
	}
	Hash是一个被所有hash函数实现的公共接口。
*/
	publicRIPEMD160 := RIPEMD160Hasher.Sum(nil)

	return publicRIPEMD160
}

// 三、（3）生成校验和
func checksum(playload []byte)[]byte  {
	firstSHA := sha256.Sum256(playload)
	secondSHA := sha256.Sum256(firstSHA[:])

	return secondSHA[:addressChecksumLen]
}

// 验证地址
func ValidateAddress(address string)bool  {
	pubKeyHash := utils.Base58Decode([]byte(address))
	actualChecksum := pubKeyHash[len(pubKeyHash)-addressChecksumLen:]
	version := pubKeyHash[0]
	pubKeyHash = pubKeyHash[1 : len(pubKeyHash)-addressChecksumLen]
	targetChecksum := checksum(append([]byte{version}, pubKeyHash...))

	return bytes.Compare(actualChecksum, targetChecksum) == 0
}


